#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os.path
import re
import pwd
import pexpect

from common.common import check_command_injection
from common.cleaner import clear
from common.common import execute_cmd, check_command_injection_ex, \
    check_sql_cmd_param, execute_cmd_oversize_return_value
from common.const import CMDResult, AuthType
from common.util.check_utils import is_port, is_ip_address
from common.exception.common_exception import ErrCodeException
from common.parse_parafile import get_env_variable
from generaldb.saphana.comm.common_util import get_value_from_str_lines, get_hostnames_in_env, log, log_start, \
    get_value_from_str_lines_with_filter, get_agent_ips_str, get_db_distributed_nodes_str
from generaldb.saphana.comm.saphana_const import SaphanaErrorCode, SaphanaModel, SaphanaConst, SaphanaJsonConstant, \
    SaphanaDbActionType
from generaldb.saphana.comm.saphana_common_function import CommonFuction
from generaldb.saphana.comm.saphana_resource_param import SaphanaResourceParam
from saphanay.resource.saphana import SAPHANA

ACTION_EXPECT = {
    SaphanaDbActionType.QUERY_CMD:
    ["lines", "rows selected", "row selected", "SQLSTATE"],
    SaphanaDbActionType.PROTECT_CMD:
    ["rows affected", "row affected", "SQLSTATE"]
}


class SaphanaClusterManage:
    def __init__(self, params: dict, pid):
        """
        初始化函数
        params：类型为字典，如{"system_id":"a00", "system_db_user":"system"}
        """
        self._sid = params.get(SaphanaJsonConstant.SYSTEM_ID)
        if not CommonFuction.saphana_check_os_user(self._sid):
            raise ErrCodeException(SaphanaErrorCode.SYSTEM_ID_NOT_EXIST,
                                   message="system id adm not exist")
        self._system_db_user_name = params.get(
            SaphanaJsonConstant.SYSTEM_DB_USER, "")
        self._system_db_port = params.get(SaphanaJsonConstant.SYSTEM_DB_PORT,
                                          0)
        self._db_name = params.get(SaphanaJsonConstant.PROTECT_DATABASE)
        self._system_db_auth_type = params.get(
            SaphanaJsonConstant.SYSTEMDB_AUTH_TYPE,
            AuthType.APP_PASSWORD.value)
        self._protect_db_auth_type = params.get(
            SaphanaJsonConstant.PROTECTDB_AUTH_TYPE,
            AuthType.APP_PASSWORD.value)
        self._pid = pid

    @staticmethod
    def expect_for_saphana(expect_process, query_cmd, output):
        exec_expect = ['rows affected', 'row affected']
        error_code = False
        default_ret = False, error_code, output
        expect_process.sendline("\pa")
        expect_process.sendline(query_cmd[0])
        try:
            expect_process.expect(exec_expect, timeout=30)
        except Exception as err:
            log.error(f"Execute cmd failed for {err}")
            expect_process.close()
            return default_ret
        expect_process.sendline("\pa")
        expect_process.sendline(query_cmd[1])
        try:
            expect_process.expect(exec_expect, timeout=30)
        except Exception as err:
            log.error(f"Execute cmd failed for {err}")
            expect_process.close()
            return default_ret
        expect_process.close()
        ret = True if not expect_process.exitstatus else False
        return True, ret, str(output)

    @staticmethod
    def erase_tail(input_value: str):
        """
        功能：去掉SQL命令查询结果里尾部的特殊字符。如果['"YES"\x1b[m\r', '\x1b[7m']， 这里"\x1b[m\r'都是特殊字符
        """
        input_value = input_value.encode('unicode_escape').decode('utf-8')
        input_value = input_value.strip("\"")
        pos = input_value.find("\"")
        if pos != -1:
            input_value = input_value[:pos]
        pos = input_value.find("\\r")
        if pos != -1:
            input_value = input_value[:pos]
        pos = input_value.find("\\x")
        if pos != -1:
            input_value = input_value[:pos]
        return input_value

    @staticmethod
    def exec_interactive_cmd(db_name, cmd, db_pwd, query_cmd, ac_type):
        """
        执行交互式命令，主要用于需要输入密码的场景，返回值为： 登录结果，命令执行结果，输出
        :param db_name 连接的数据库名称
        :param cmd 连接数据库的命令
        :param db_pwd 连接数据库的命令时交互式输入的密码
        :param query_cmd 连接成功后，可以执行的sql，如果为空则不执行
        :param ac_type query_cmd的sql是查询还是非查询。影响了是否要设置超时时间
        """
        exec_expect = ACTION_EXPECT.get(ac_type)
        default_ret = False, False, ""
        if check_command_injection(cmd):
            return default_ret
        # 如果数据库的环境磁盘满了，就可以卡死
        expect_process = pexpect.spawn(cmd, timeout=None)
        try:
            expect_process.expect("Password:", timeout=30)
        except Exception as err:
            expect_process.close()
            return default_ret
        real_pwd_value = get_env_variable(db_pwd)
        if not real_pwd_value:
            log.error(f"Fail to get {db_name} pwd.")
            expect_process.close()
            return default_ret
        expect_process.sendline(real_pwd_value)
        clear(real_pwd_value)
        try:
            expect_process.expect([f"{db_name.upper()}=>", "...=>"], timeout=30)
        except Exception as err:
            log.error(f"Execute cmd failed for {err}")
            expect_process.close()
            return default_ret
        output = ""
        log.debug(f"Query cmd: {query_cmd}, expect: {exec_expect}")
        if query_cmd and isinstance(query_cmd, list):
            return SaphanaClusterManage.expect_for_saphana(expect_process, query_cmd, output)
        # query_cmd 为字符串时
        output, ret = SaphanaClusterManage.expect_for_saphana_str(ac_type, db_name, exec_expect, expect_process,
                                                                  query_cmd)
        return True, ret, str(output)

    @staticmethod
    def expect_for_saphana_str(ac_type, db_name, exec_expect, expect_process, query_cmd):
        output = ""
        if query_cmd:
            expect_process.sendline("\pa")
            expect_process.sendline(query_cmd)
            # 如果是查询请求，则设置超时时间
            if ac_type == SaphanaDbActionType.QUERY_CMD:
                expect_process.expect(exec_expect, timeout=30)
            else:
                expect_process.expect(exec_expect)
            output = expect_process.before.decode()
            db_name_max_len = 10  # 能完整显示的数据库名称最大长度
            cut_str = f"hdbsql {db_name.upper()[0:db_name_max_len]}...=>"
            if len(db_name) <= db_name_max_len:
                cut_str = f"hdbsql {db_name.upper()}=>"
            output = output[output.rfind(cut_str) + len(cut_str):]
            if output.find("OFF") != -1:
                cut_str = "hdbsql =>"
                output = output[output.rfind(cut_str) + len(cut_str):]
            log.info(f"Exec query cmd ret : {[output]}")
        try:
            expect_process.close()
        except Exception as err:
            log.warn(f"Close expect fail for {err}")
        ret = True if not expect_process.exitstatus else False
        return output, ret

    @staticmethod
    def exec_hdbuserstore_cmd(db_name, cmd, user_store_key, query_cmd,
                              ac_type):
        """
        使用hdbuserstore key执行命令，返回值为： 命令执行结果，输出
        :param cmd
        :param db_name 连接的数据库名称
        :param user_store_key
        :param query_cmd 连接成功后，可以执行的sql，如果为空则不执行
        :param ac_type query_cmd的sql是查询还是非查询。影响了是否要设置超时时间
        """
        exec_expect = ACTION_EXPECT.get(ac_type)
        default_ret = False, False, ""
        if check_command_injection(cmd):
            log.error("Cmd is illegal.")
            return default_ret
        # 如果数据库的环境磁盘满了，就可能卡死
        tmp_key = get_env_variable(user_store_key)
        expect_process = pexpect.spawn(f"{cmd} -c 'hdbsql -d {db_name} -U {tmp_key}'",
                                       timeout=None)
        clear(tmp_key)
        try:
            expect_process.expect("hdbsql=>", timeout=30)
        except Exception as err:
            log.error(f"Execute cmd failed for {err}")
            expect_process.close()
            return default_ret
        expect_process.sendline("")
        try:
            expect_process.expect([f"{db_name.upper()}=>", "...=>"], timeout=30)
        except Exception as err:
            log.error(f"Execute cmd failed for {err}")
            expect_process.close()
            return default_ret
        output = ""
        log.debug(f"Query cmd: {query_cmd}, expect: {exec_expect}")
        if query_cmd:
            expect_process.sendline("\pa")
            expect_process.sendline(query_cmd)
            # 如果是查询请求，则设置超时时间
            if ac_type == SaphanaDbActionType.QUERY_CMD:
                expect_process.expect(exec_expect, timeout=30)
            else:
                expect_process.expect(exec_expect)
            output = expect_process.before.decode()
            log.debug(f"{output}")
            cut_str = f"hdbsql {db_name.upper()}=>"
            log.debug(f"Exec query cmd before cut: {output}, cutstr: {cut_str}")
            output = output[output.rfind(cut_str) + len(cut_str):]
            if output.find("OFF") != -1:
                cut_str = "hdbsql =>"
                output = output[output.rfind(cut_str) + len(cut_str):]
            log.info(f"Exec query cmd ret : {output}")
        try:
            expect_process.close()
        except Exception as err:
            log.warn(f"Close expect fail for {err}")
        ret = True if not expect_process.exitstatus else False
        return True, ret, str(output)
    
    def get_node_list(self):
        default_ret = False, []
        instance_id = self.get_instance_id()
        if not instance_id:
            return default_ret
        cmd = f"su - {self._sid}adm -c 'sapcontrol -nr {instance_id} -function GetSystemInstanceList'"
        ret, output, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            log.error(f"Fail to get instance list for {err}")
            return default_ret
        host_list = [item.split(',')[0] for item in output.split('\n') if "HDB" in item]
        if not host_list:
            log.error(f"Fail to get instance list with output : {output}")
            return default_ret
        return True, host_list

    def execute_query_in_db(self,
                            db_name,
                            db_user="",
                            db_pwd="",
                            query_cmd="",
                            ac_type=SaphanaDbActionType.QUERY_CMD):
        """
        功能：从指定数据库中执行指定动作
        参数：
            db_name：要登录的数据库
            db_user：要登录的数据库用户名
            db_pwd:登录的密码或者KEY
            query_cmd：指定动作
            ac_type：动作类型
        返回值：True/False,查询结果
        """
        default_ret = False, ""
        instance_id = self.get_instance_id()
        if not instance_id:
            return default_ret
        # 获取所有节点列表
        if not CommonFuction().saphana_check_param_safty(
            [instance_id, db_name, db_user]):
            log.error(f"Param is illeg.")
            return default_ret
        ret, host_list = self.get_node_list()
        if not ret:
            return default_ret
        return_res = False
        output = ""
        # 判断目标数据库是否为系统数据库且获取其鉴权方式
        auth_type = self._system_db_auth_type
        if (self.get_model() == SaphanaModel.MULTI_TENANT_MODE) and (
                db_name.upper() != SaphanaConst.SYSTEM_DB):
            auth_type = self._protect_db_auth_type
        if int(auth_type) == AuthType.OTHER.value:
            cmd = f"su - {self._sid}adm"
            login_ret, cmd_exec_ret, output = \
                SaphanaClusterManage.exec_hdbuserstore_cmd(db_name, cmd, db_pwd, query_cmd, ac_type)
            return login_ret and cmd_exec_ret, str(output)
        for host in host_list:
            if not CommonFuction().saphana_check_param_safty([host]):
                log.error(f"Param host is illeg.")
                return default_ret
            SaphanaResourceParam.check_param_is_contain_space(
                [db_name, db_user])
            if not is_port(self._system_db_port):
                log.error(f"Port is illeg.")
                return default_ret
            cmd = f"su - {self._sid}adm -c " \
                  f"'hdbsql -i {instance_id} -n {host}:{self._system_db_port} -d {db_name} -u {db_user}'"
            login_ret, cmd_exec_ret, output = SaphanaClusterManage.exec_interactive_cmd(
                db_name, cmd, db_pwd, query_cmd, ac_type)
            if login_ret:
                log.info(f"Login successfully:{host}.")
                return_res = login_ret and cmd_exec_ret
                break

        return return_res, str(output)

    def get_instance_id(self):
        cmd = f"su - {self._sid}adm -c 'echo $TINSTANCE'"
        ret, output, err = execute_cmd(cmd)
        if ret == CMDResult.SUCCESS:
            instance_id = output.strip()
            log.debug(f"The instance id is {output}")
            if not CommonFuction().saphana_check_param_safty([instance_id]):
                log.error(f"Instance id is illeg.")
                raise ErrCodeException(SaphanaErrorCode.ERROR_PARAM,
                                       message="instance id is illeg.")
            return str(instance_id)
        log.error(f"Fail to get instance id for {self._sid} with err {err}")
        if f"user {self._sid}adm does not exist" in err:
            raise ErrCodeException(SaphanaErrorCode.SYSTEM_ID_NOT_EXIST,
                                   message="system id not exists.")
        return ""

    @log_start()
    def login_database(self, db_user_name, db_pwd):
        ret, _ = self.execute_query_in_db(self._db_name, db_user_name, db_pwd)
        return ret

    def log_specify_database(self, dn_name, db_user_name, db_pwd):
        ret, _ = self.execute_query_in_db(dn_name, db_user_name, db_pwd)
        return ret

    def get_instance_user_group(self):
        return pwd.getpwnam(f"{self._sid}adm").pw_uid, pwd.getpwnam(
            f"{self._sid}adm").pw_gid

    def get_database_running_status(self, system_db_pwd):
        """
        功能：查询指定数据库的运行状态
        参数：
            db_name：查查询的数据库名称
        返回值：
            True：正在运行
            False：未运行或者查询失败
        """
        if not check_sql_cmd_param(self._db_name):
            return False
        query_cmd = f"select active_status from M_DATABASES where database_name='{self._db_name.upper()}';"
        login_db_name = SaphanaConst.SYSTEM_DB if self.is_multi_system(
        ) else self._sid
        ret, output = self.execute_query_in_db(login_db_name,
                                               self._system_db_user_name,
                                               system_db_pwd, query_cmd)
        target_output_line = 3  # 一行查询命令，一行属性，一行值
        log.debug(f"Query status output: {output}")
        if not ret or len(output.split("\n")) <= target_output_line:
            log.error(f"Fail to query {self._db_name} status.")
            return False
        status = SaphanaClusterManage.erase_tail(output.split("\n")[2])
        ret = True if status == "YES" else False
        return ret

    def get_db_nodes_and_services_list(self, db_user, db_pwd):
        """
        获取指定数据库分布的节点以及每个节点上的服务
        返回值:
        {hostname:["nameserver", "indexserver",]}
        """
        query_cmd = "select host,service_name from m_services;"
        ret, output = self.execute_query_in_db(self._db_name, db_user, db_pwd,
                                               query_cmd)
        if not ret:
            log.error(f"Fail to get {self._db_name} nodes and services.")
            return {}
        output = output.split("\n")
        min_lines = 3  # 一行查询命令，一行属性，N行值
        if len(output) < min_lines:
            log.error(f"Database {self._db_name} not exists.")
            raise ErrCodeException(SaphanaErrorCode.DB_NOT_EXIST,
                                   [self._db_name], "database not exists")
        result = dict()
        for item in output[2:]:
            temp_value = item.split(',')
            if len(temp_value) < 2:
                continue
            hostname = temp_value[0].strip("\"")
            service_name = SaphanaClusterManage.erase_tail(temp_value[1])
            if hostname not in result.keys():
                result[hostname] = list()
            result[hostname].append(service_name)
        return result

    def get_master_hostname(self, system_db_pwd):
        """
        功能：获取当前系统数据库为主的节点hostname
        返回值：hostname
        """
        query_cmd = "select host from m_services where coordinator_type='MASTER';"
        login_db_name = SaphanaConst.SYSTEM_DB if self.is_multi_system(
        ) else self._sid
        ret, output = self.execute_query_in_db(login_db_name,
                                               self._system_db_user_name,
                                               system_db_pwd, query_cmd)
        if not ret:
            log.error("Fail to get master hostname.")
            return ""
        output = output.split("\n")
        min_lines = 3  # 一行查询命令，一行属性，N行值
        if len(output) < min_lines:
            log.error("No records.")
            return ""
        hostname = output[2].strip("\"")
        return hostname[:hostname.find("\"")]

    def get_cluster_status(self):
        """
        获取集群状态
        :return:
        """
        ret, out, err_out = execute_cmd(
            f'su - {self._sid}adm -c "hdbnsutil -sr_state"')
        if ret != CMDResult.SUCCESS:
            log.error(
                f"Get cluster {self._sid} status failed. ret:{ret} err:{err_out}"
            )
            raise ErrCodeException(SaphanaErrorCode.DB_ENV_ERROR,
                                   message=f"Get cluster status failed.")
        cluster_status = get_value_from_str_lines(str(out), "online:", "")
        log.info(f"Get cluster {self._sid} status {cluster_status}.")
        return cluster_status

    def is_system_db(self):
        """
        判断数据库是不是系统数据库
        :return: True/False
        """
        # 如果是多租户，则对比数据库名称
        if self.get_model() == SaphanaModel.MULTI_TENANT_MODE:
            return self._db_name.upper() == SaphanaConst.SYSTEM_DB
        # 如果是单租户，则肯定是系统数据库
        else:
            return True

    def is_multi_system(self):
        """
        判断是不是多租户模式
        :return: True/False
        """
        return self.get_model() == SaphanaModel.MULTI_TENANT_MODE

    def get_model(self):
        """
        获取租户模式
        :return: str
        """
        dir_instance = self.get_dir_instance()
        global_ini_file_path = f"{dir_instance}/SYS/global/hdb/custom/config/global.ini"
        cmd = f"su - {self._sid}adm -c 'cat {global_ini_file_path}'"
        ret, output, err = execute_cmd(cmd)
        if ret == CMDResult.SUCCESS:
            return get_value_from_str_lines(str(output), "mode =", "", True)
        raise ErrCodeException(SaphanaErrorCode.DB_ENV_ERROR,
                               message="Get global ini error.")

    def check_agent_enough(self, agent_ip_list: list, db_user: str,
                           db_pwd_env_key: str, endpoint_ip_list: list):
        """
        判断数据库的数据分布的节点是否和用户选择的节点一样，如果不一样则报错
        :param agent_ip_list: 之前的节点list
        :param db_user：保护的数据库的用户名
        :param db_pwd_env_key：保护的数据库的密码的环境变量
        :param endpoint_ip_list：代理主机注册的ip list
        """
        db_distributed_nodes = self.get_db_distributed_nodes(
            db_user, db_pwd_env_key)
        if len(agent_ip_list) != len(db_distributed_nodes):
            raise ErrCodeException(
                SaphanaErrorCode.DB_NODES_NOT_FULL_MATCH,
                get_db_distributed_nodes_str(db_distributed_nodes,
                                             get_hostnames_in_env()),
                get_agent_ips_str(endpoint_ip_list),
                message=f"Instance nodes {agent_ip_list} not full match {db_distributed_nodes}."
            )

        # 读取/etc/hosts文件的内容
        hostname_file_content = get_hostnames_in_env()
        log.info(F"Dis: {db_distributed_nodes}")

        # 判断node是否完全匹配
        for node_hostname in db_distributed_nodes:
            node_ip = get_value_from_str_lines_with_filter(
                str(hostname_file_content), node_hostname, [
                    "#", "ipv6-localhost", "ipv6-loopback", "ipv4-localhost",
                    "ipv4-loopback", "127.0.0.1"
                ])
            log.debug(f"Node ip: {node_ip}")
            if not node_ip:
                raise ErrCodeException(
                    SaphanaErrorCode.DB_NODES_NOT_FULL_MATCH,
                    get_db_distributed_nodes_str(db_distributed_nodes,
                                                 get_hostnames_in_env()),
                    get_agent_ips_str(endpoint_ip_list),
                    message=f"Instance node {node_hostname} no mapper ip.")
            is_match_ip = False
            for agent_ips_str in agent_ip_list:
                if agent_ips_str.find(node_ip) != -1:
                    is_match_ip = True
                    break
            if not is_match_ip:
                raise ErrCodeException(
                    SaphanaErrorCode.DB_NODES_NOT_FULL_MATCH,
                    get_db_distributed_nodes_str(db_distributed_nodes,
                                                 get_hostnames_in_env()),
                    get_agent_ips_str(endpoint_ip_list),
                    message=f"Instance node {node_ip} not in select nodes.")
        log.info("Check instance node full match success.")

    def get_db_distributed_nodes(self, tenant_user_name: str,
                                 tenant_password: str):
        """
        功能：获取指定数据库的数据分布的节点。系统数据库从GetSystemInstanceList中获取；租户数据库从M_SERVICES视图里查询
        :param tenant_user_name：租户用户名，当指定的数据库为租户数据库时使用
        :param tenant_password：租户密码，当指定的数据库为租户数据库时使用
        返回值：字符串数组
        """
        system_nodes = []
        if self.is_system_db():
            # 读取集群nodes所有的hostname
            ret, out, err_out = execute_cmd(
                f'su - {self._sid}adm -c '
                f'"sapcontrol -nr {self.get_instance_id()} '
                f'-function GetSystemInstanceList"')
            if ret != CMDResult.SUCCESS:
                log.error(
                    f"Get instance {self._sid} db {self._db_name} nodes failed. ret:{ret} err:{err_out}"
                )
                raise ErrCodeException(SaphanaErrorCode.ACCESS_DB_ERROR,
                                       message="Get instance db nodes failed.")
            is_ok = False
            for line in out.splitlines():
                if line == "OK":
                    is_ok = True
                ele = line.split(",")
                if len(ele) == SaphanaConst.INSTANCE_INFO_ELE_SIZE and (
                        not line.startswith("hostname, instanceNr")):
                    system_nodes.append(ele[0])
            if not is_ok:
                raise ErrCodeException(SaphanaErrorCode.ACCESS_DB_ERROR,
                                       message="Get instance db nodes failed.")
            return system_nodes
        # 查询数据库，获取备份的数据库所涉及的节点
        alias_name = "HANA_DB_HOST"
        sql = f"select DISTINCT HOST AS {alias_name} FROM M_SERVICES;"
        ret, select_result = self.execute_query_in_db(self._db_name,
                                                      tenant_user_name,
                                                      tenant_password, sql)
        select_result = re.sub('\x1b.*?m', '', select_result)
        if not ret:
            raise ErrCodeException(SaphanaErrorCode.ACCESS_DB_ERROR,
                                   message="Get instance db nodes failed.")
        for line in select_result.splitlines():
            if line.find(sql) != -1 or line == alias_name or not line:
                continue
            if line == alias_name:
                continue
            system_nodes.append(line.replace('"', ''))
        log.info(f"Get db {self._db_name} distributed nodes: {system_nodes}")
        return system_nodes[:-1]

    def get_dir_instance(self):
        """
        获取dir instance环境变量，并去除最后一个元素
        :return: eg: :/usr/sap/S00
        """
        cmd = f"su - {self._sid}adm -c 'echo $DIR_INSTANCE'"
        ret, output, err = execute_cmd(cmd)
        if ret == CMDResult.SUCCESS:
            db_dir = ""
            ele_arr = output.split("/")
            ele_arr.pop()
            for ele in ele_arr:
                if ele != "":
                    db_dir = db_dir + "/" + ele
            log.debug(f"The dir instance is {db_dir}")
            return db_dir
        log.error(f"Fail to get dir instance for {self._sid} with err {err}")
        if f"user {self._sid}adm does not exist" in err:
            raise ErrCodeException(SaphanaErrorCode.SYSTEM_ID_NOT_EXIST,
                                   message="system id adm not exist")
        raise ErrCodeException(SaphanaErrorCode.DB_ENV_ERROR,
                               message="Get dir instance error.")

    def get_dir_instance_ex(self):
        """
        获取dir instance环境变量
        :return: eg: /usr/sap/X00/HDB00
        """
        cmd = f"su - {self._sid}adm -c 'echo $DIR_INSTANCE'"
        ret, output, err = execute_cmd(cmd)
        if ret == CMDResult.SUCCESS:
            log.info(f"The dir instance is {output}")
            return output.strip()
        log.error(f"Fail to get dir instance for {self._sid} with err {err}")
        if f"user {self._sid}adm does not exist" in err:
            raise ErrCodeException(SaphanaErrorCode.SYSTEM_ID_NOT_EXIST,
                                   message="system id adm not exist")
        raise ErrCodeException(SaphanaErrorCode.DB_ENV_ERROR,
                               message="Get dir instance error.")

    def get_uuid(self):
        """
        获取集群的唯一流水号
        :return: str
        """
        dir_instance = self.get_dir_instance()
        nameserver_ini_file_path = f"{dir_instance}/SYS/global/hdb/custom/config/nameserver.ini"
        cmd = f"su - {self._sid}adm -c 'cat {nameserver_ini_file_path}'"
        ret, output, err = execute_cmd(cmd)
        if ret == CMDResult.SUCCESS:
            return get_value_from_str_lines(str(output), "id =", "", True)
        raise ErrCodeException(SaphanaErrorCode.DB_ENV_ERROR,
                               message="Get nameserver ini error.")

    def get_version(self):
        """
        获取集群的版本号
        :return: str
        """
        cmd = f"su - {self._sid}adm -c 'HDB version'"
        ret, output, err = execute_cmd(cmd)
        if ret == CMDResult.SUCCESS:
            return get_value_from_str_lines(str(output), "version:", "")
        raise ErrCodeException(SaphanaErrorCode.DB_ENV_ERROR,
                               message="Get version error.")

    def get_shared_path(self) -> str:
        """
        获取集群的共享路径
        :return: str
        """
        instance_dir = self.get_dir_instance_ex()
        shared_path = os.path.realpath(instance_dir).split("/shared")[0]
        return shared_path

    @log_start()
    def all_db_services_running(self, db_user, db_pwd):
        """
        功能：判断目标数据库所有的服务是否都正常运行
        返回值： ret, 未运行的服务列表（如[hana1.daemon, hana2.indexserver]）
        True：正常运行；False：至少有一个未运行
        """
        if self.is_system_db():
            master_hostname = self.get_master_hostname(db_pwd)
            query_cmd = f"select host,service_name from m_services where host='{master_hostname}' " \
                        f"and active_status!='YES';"
        else:
            query_cmd = "select host,service_name from m_services where active_status!='YES';"
        real_pwd_value = get_env_variable(db_pwd)
        saphana = SAPHANA(self._sid, self._db_name, self._system_db_port, db_user, real_pwd_value)
        output = saphana.execute_sql_command(query_cmd)
        if len(output) == 0:
            return True, []
        unavail_service_list = []
        for record in output:
            if len(record) != 2:
                continue
            unavail_service_list.append(
                f"{SaphanaClusterManage.erase_tail(record[0])}."
                f"{SaphanaClusterManage.erase_tail(record[1])}")
        return False, unavail_service_list

    def get_tenant_master_hostname(self, db_user, db_pwd):
        """
        功能：获取租户数据库中indexserver为master的节点名称
        """
        query_cmd = "select host from m_services where coordinator_type='MASTER' and service_name='indexserver';"
        ret, output = self.execute_query_in_db(self._db_name, db_user, db_pwd,
                                               query_cmd)
        if not ret:
            log.error("Fail to get master hostname.")
            return ""
        output = output.split("\n")
        min_lines = 3  # 一行查询命令，一行属性，N行值
        if len(output) < min_lines:
            log.error("No records.")
            return ""
        hostname = output[2].strip("\"")
        return hostname[:hostname.find("\"")]

    def is_tenant_db_exist(self, system_db_pwd):
        """
        判断目标租户数据库是否存在
        """
        if not check_sql_cmd_param(self._db_name):
            return False
        query_cmd = f"select DATABASE_NAME from M_DATABASES where database_name='{self._db_name.upper()}';"
        ret, output = self.execute_query_in_db(self._db_name.upper(),
                                               self._system_db_user_name,
                                               system_db_pwd, query_cmd)
        target_output_line = 3  # 一行查询命令，一行属性，一行值
        log.debug(f"Query status output: {output}")
        if not ret or len(output.split("\n")) <= target_output_line:
            log.error(f"DB {self._db_name} not exist.")
            return False
        return True

    def is_tenant_db_exist_for_restore(self, system_db_pwd):
        """
        从系统数据库中判断目标租户数据库是否存在
        """
        if not check_sql_cmd_param(self._db_name):
            return False
        query_cmd = f"select DATABASE_NAME from M_DATABASES where database_name='{self._db_name.upper()}';"
        ret, output = self.execute_query_in_db(SaphanaConst.SYSTEM_DB,
                                               self._system_db_user_name,
                                               system_db_pwd, query_cmd)
        target_output_line = 3  # 一行查询命令，一行属性，一行值
        log.debug(f"Query status output: {output}")
        if not ret or len(output.split("\n")) <= target_output_line:
            log.error(f"DB {self._db_name} not exist.")
            return False
        return True

    def check_hdbuserstorekey(self, user_key):
        cmd = f"su - {self._sid}adm -c 'hdbuserstore LIST'"
        ret, output, _ = execute_cmd_oversize_return_value(cmd)
        if ret != CMDResult.SUCCESS:
            log.error("Fail to select key.")
            return False
        output = output.split("\n")
        for output_line in output:
            if output_line == "":
                continue
            key_information = output_line.split(" ")
            if key_information[0] != "KEY":
                continue
            if key_information[1] == user_key.upper():
                return True
        return False

    def change_system_config(self, config_cmd, system_db_pwd):
        """
        用于修改系统的配置 global.ini
        config_cmd：修改的命令
        """
        login_db_name = SaphanaConst.SYSTEM_DB if self.is_multi_system() else self._sid
        ret, output = self.execute_query_in_db(login_db_name,
                                               self._system_db_user_name,
                                               system_db_pwd, config_cmd,
                                               SaphanaDbActionType.PROTECT_CMD)
        if not ret:
            log.error(f"Fail to change config for %s", output)
        return ret

    def db_name_prefix(self) -> str:
        prefix = "" if self.is_system_db() else "DB_"
        return prefix
